package com.tjetc;

import com.tjetc.annotation.*;
import com.tjetc.entity.BeanDefinition;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IOC容器类
 *
 * @author gaoyongxiang
 * @version v1.0
 * @date 2025/9/3
 */
@SuppressWarnings("all")
public class MyAnnotationConfigApplicationContext {

    // 获取外部传进来的配置类
    private Class configClass;

    // 用来保存bean的定义信息 key：beanName value：bean的定义信息
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();


    //用来保存生成单例对象的集合 key：beanName value：对应beanName的对象实例
    // 因为我们不知道将来是放什么类型的对象 所以这里用Object替代
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();


    // 这个地方原生是从getBean中拿到的 但是我们只是为了流程简化操作
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public MyAnnotationConfigApplicationContext(Class configClass) {
        this.configClass = configClass;
        // IOC容器的创建过程

        // 扫描并生成bean的定义信息
        componentScanByConfig(configClass);

        // 将生成的单例对象放入map中

        // 返回所有key的枚举
        Enumeration<String> keys = beanDefinitionMap.keys();

        while (keys.hasMoreElements()) {
            // 获取对应的beanName
            String beanName = keys.nextElement();
            // 通过beanName找到对应bean的定义信息
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            // 此时需要判断该对象的定义信息是不是单例的
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                // 生成bean对象
                Object bean = createBean(beanDefinition, beanName);
                singletonObjects.put(beanName, bean);
            }
        }

//        System.out.println(singletonObjects);

    }

    /**
     * 根据bean的名称获取对应的bean对象
     *
     * @param beaName bean名称
     * @return 返回的bean对象
     */
    public Object getBean(String beaName) {
        // 判断bean是否存在
        if (beanDefinitionMap.containsKey(beaName)) {
            // 获取到bean的定义信息
            BeanDefinition beanDefinition = beanDefinitionMap.get(beaName);
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                // 如果该对象是单例的直接从单例map返回
                return singletonObjects.get(beaName);
            } else {
                // 如果不是单例就需要重新创建一个对象返回
                return createBean(beanDefinition, beaName);
            }
        } else {
            // 如果bean此时不存在 就抛异常
            throw new RuntimeException("bean不存在");
        }
    }


    /**
     * 根据bean的定义信息创建对应的对象实例
     *
     * @param beanDefinition 对象的定义信息
     * @return 实例对象
     */
    private Object createBean(BeanDefinition beanDefinition, String beanName) {
        // 先从bean的定义信息中拿到clazz对象 通过反射创建
        Class clazz = beanDefinition.getClazz();
        try {
            // 生成对象
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // 返回对象之前 要判断该对象是否需要进行注入（DI）

            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                // 判断该属性是否存在Autowired注解
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    // 我们就直接根据名称进行注入
                    // 获取属性名称
                    String fieldName = declaredField.getName();
                    // 根据属性名获取对应的bean对象
                    Object bean = getBean(fieldName);
                    // 进行注入 instance.setXXX(bean);
                    // 注意：因为是private方法 所以需要暴力破解 但是注意破解设置完毕后 要恢复
                    declaredField.setAccessible(true);
                    declaredField.set(instance, bean);
                    declaredField.setAccessible(false);
                }
            }

            // 遍历BeanPostProcessor集合 调用对应的postProcessBeforeInitialization方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                Object bean = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
                // 因为该方法最终需要返回一个bean对象
                instance = bean;
            }



            // 判断当前创建的bean对象是否实现了InitializingBean接口
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }

            // 遍历BeanPostProcessor集合 调用对应的postProcessAfterInitialization方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                Object bean = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
                // 因为该方法最终需要返回一个bean对象
                instance = bean;
            }


            return instance;
        } catch (Exception e) {

        }
        return null;
    }


    /**
     * 根据配置类扫描类
     *
     * @param configClass 配置类信息
     */
    private void componentScanByConfig(Class configClass) {

        // 1. 获取到配置类中的扫描包的注解
        ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        // 2. 获取注解中的value值，获取到对应的扫描路径
        String path = componentScan.value();
        // com.tjetc
//        System.out.println(path);
        // 3. 获取该路径下所有需要注入的bean的class
        // (1) 先获取app类加载器
        ClassLoader classLoader = MyAnnotationConfigApplicationContext.class.getClassLoader();
        // (2) 通过类加载器找到所有类的url路径
        // 注意：包路径是/  而我们获取到路径是. 所以我们需要转换
        path = StringUtils.replaceChars(path, ".", "/");
        // com/tjetc
//        System.out.println(path);
        URL url = classLoader.getResource(path);
        // file:/Users/gaoyongxiang/baidupan/IDEA-Java/202507-spring/mySpring/target/classes/com/tjetc
//        System.out.println(url);
        // (3) 得到该路径下的所有的文件 简化操作 不做递归查询（拓展）
        File file = new File(url.getPath());
        // (4) 判断该文件是目录还是文件
        if (file.isDirectory()) {
            // 拿到该目录下的所有文件
            File[] files = file.listFiles();
//            System.out.println(ArrayUtils.toString(files));
            // 遍历所有的文件
            for (File tempFile : files) {
                // 判断文件是不是class文件 先拿到文件名称
                String fileName = tempFile.getName();
                if (fileName.endsWith(".class")) {
                    // 可以拿到类名
                    String className = fileName.substring(0, fileName.length() - 6);
//                    System.out.println(className);
                    // 根据类名拿到类的全限定路径 根据类的全限定路径 生成类对象
                    String fullClassName = StringUtils.replaceChars(path, "/", ".") + "." + className;
//                    System.out.println(fullClassName);
                    try {
                        Class clazz = classLoader.loadClass(fullClassName);
                        // 根据类对象判断该类是否有我们想要的注解 这里面简化 我们只判断是否是component注解
                        if (clazz.isAnnotationPresent(Component.class)) {
                            // 将后置处理添加到集合中
                            // 这里不能用instanceof来判断是否是该类型 因为当前的对象是clazz 不是一个真正的实例
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                // 通过反射创建对应的后置处理器
                                BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                                // 添加到集合中
                                beanPostProcessors.add(beanPostProcessor);
                                // 如果该对象已经被添加到beanPostProcessors集合中，那么就不用添加到对象集合了
                                continue;
                            }

                            // 得到bean的class对象
//                            System.out.println(clazz);

                            // 4. 获取beanName 并放入集合中
                            // 获取Component注解
                            Component component = (Component) clazz.getDeclaredAnnotation(Component.class);
                            // 获取Component注解中的value值
                            String beanName = component.value();
                            if (StringUtils.isBlank(beanName)) {
                                // 如果为空 则用当前类名的首字母小写进行创建
                                beanName = StringUtils.uncapitalize(className);
                            }
//                            System.out.println(beanName);
                            // 构建BeanDefinition对象并放入map集合中
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            // 判断该类中是否存在Scope注解
                            if (clazz.isAnnotationPresent(Scope.class)) {
                                // 如果存在就按照写入的scope值进行设置
                                Scope scope = (Scope) clazz.getDeclaredAnnotation(Scope.class);
                                String scopeValue = scope.value();
                                beanDefinition.setScope(scopeValue);
                            } else {
                                // 如果没有socpe注解那就按照单例注入
                                beanDefinition.setScope("singleton");
                            }
                            // 注入map中
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }
                    } catch (Exception e) {

                    }

                }
            }
//            System.out.println(beanDefinitionMap);
//            System.out.println(beanPostProcessors);
        }
    }
}